package org.pitest.project;

import org.pitest.project.impl.CommandFileConfigurationParser;
import org.pitest.project.impl.XmlProjectConfigurationParser;

import java.util.HashMap;
import java.util.Map;

/**
 * The ProjectConfigurationParserFactory is responsible for creating a
 * ProjectConfigurationParser instance based on a system property.
 * <p/>
 * If the system property is not set, then the default implementation of the
 * ProjectConfigurationParser is used instead.
 *
 * @author Aidan Morgan
 */
public class ProjectConfigurationParserFactory {
  /**
   * The name of the system property that is used to override the default
   * ProjectConfigurationParser implementation.
   */
  public static final String PARSER_PROPERTY = "projectConfigurationParser.impl";

  /**
   * The name of the default ProjectConfigurationParser instance to use.
   */
  public static final String DEFAULT_PARSER = XmlProjectConfigurationParser.class
      .getName();

  /**
   * A {@see Map} that contains parser names to {@see Class} that will parse that file type.
   */
  private static Map<String, Class> parserTypes = new HashMap<String, Class>();

  static {
    // the default XML configuration file parser
    parserTypes.put("xml", XmlProjectConfigurationParser.class);
    // a parser that allows command line arguments to be specified in a file
    parserTypes.put("cmd", CommandFileConfigurationParser.class);
  }

  /**
   * Creates a new ProjectConfigurationParser instance, based on the system
   * property. If no system property is specified then the default
   * ProjectConfigurationParser instance is returned.
   *
   * @return a new ProjectConfigurationParser for parsing project files.
   * @throws ProjectConfigurationParserException
   *          if an exception occurs instantiating the
   *          ProjectConfigurationParser instance.
   */
  public static ProjectConfigurationParser createParser() throws ProjectConfigurationParserException {
    final String propertyValue = System.getProperty(PARSER_PROPERTY, DEFAULT_PARSER);

    return createParserInstance(propertyValue);
  }

  private static ProjectConfigurationParser createParserInstance(String className) throws ProjectConfigurationParserException {
    try {
      final Class<?> parserClass = Class.forName(className);
      final Object instance = parserClass.newInstance();

      // if the new object isn't an instance of the ProjectConfigurationParser
      // interface, then throw an exception.
      if (instance instanceof ProjectConfigurationParser) {
        return (ProjectConfigurationParser) instance;
      } else {
        throw new ProjectConfigurationParserException(
            "Cannot create ProjectConfigurationParser instance from class "
                + className + " as it does not implement "
                + ProjectConfigurationParser.class.getName() + ".");
      }
    } catch (ClassNotFoundException e) {
      throw new ProjectConfigurationParserException(e);
    } catch (InstantiationException e) {
      throw new ProjectConfigurationParserException(e);
    } catch (IllegalAccessException e) {
      throw new ProjectConfigurationParserException(e);
    }
  }

  /**
   * Creates a new {@see ProjectConfigurationParser} instance based on the provided {@see type}. The {@see type} is
   * associated with a {@see Map} of known {@see ProjectConfigurationParser} instances, and the instance in the mapping
   * associated with the provided {@see type} is returned.
   * <p/>
   * If the {@see type} is {@code null}, or empty then a {@see ProjectConfigurationParserException} is thrown.
   * <p/>
   * If the {@see type} is not known then a {@see ProjectConfigurationParserException} is thrown.
   *
   * @param type the type of the bundled {@see ProjectConfigurationParser} to create.
   * @return a {@see ProjectConfigurationParser} instance of the provided {@see type}.
   * @throws ProjectConfigurationParserException
   *          if the {@see type} is null or empty, of if the {@see type} is unknown.
   */
  public static ProjectConfigurationParser createParserForType(String type) throws ProjectConfigurationParserException {
    String val = type.trim().toLowerCase();

    if (val == null || val.length() == 0) {
      throw new ProjectConfigurationParserException("A parser type must be specified.");
    }

    Class cl = parserTypes.get(val);

    if (cl == null) {
      throw new ProjectConfigurationParserException("Unknwon parser type: " + val + ".");
    }

    return createParserInstance(cl.getName());
  }
}
